// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Package generate实现“go generate”命令。
package generate

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"go/parser"
	"go/token"
	exec "internal/execabs"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/load"
	"cmd/go/internal/modload"
	"cmd/go/internal/str"
	"cmd/go/internal/work"
)

var CmdGenerate = &base.Command{
	Run:       runGenerate,
	UsageLine: "go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages]",
	Short:     "generate Go files by processing source",
	Long: `
Generate runs commands described by directives within existing
files. Those commands can run any process but the intent is to
create or update Go source files.

Go generate is never run automatically by go build, go test,
and so on. It must be run explicitly.

Go generate scans the file for directives, which are lines of
the form,

	// go:生成命令参数。。。快走“）where command 

is the generator to be run, corresponding to an executable file
that can be run locally. It must either be in the shell path
(gofmt), a fully qualified path (/usr/you/bin/mytool), or a
command alias, described below.

Note that go generate does not parse the file, so lines that look
like directives in comments or multiline strings will be treated
as directives.

The arguments to the directive are space-separated tokens or
double-quoted strings passed to the generator as individual
arguments when it is run.

Quoted strings use Go syntax and are evaluated before execution; a
quoted string appears as a single argument to the generator.

To convey to humans and machine tools that code is generated,
generated source should have a line that matches the following
regular expression (in Go syntax):

	^// Code generated.*不要编辑\.$

This line must appear before the first non-comment, non-blank
text in the file.

Go generate sets several variables when it runs the generator:

	$GOARCH
		The execution architecture (arm, amd64, etc.)
	$GOOS
		The execution operating system (linux, windows, etc.)
	$GOFILE
		The base name of the file.
	$GOLINE
		The line number of the directive in the source file.
	$GOPACKAGE
		The name of the package of the file containing the directive.
	$DOLLAR
		A dollar sign.

Other than variable substitution and quoted-string evaluation, no
special processing such as "globbing" is performed on the command
line.

As a last step before running the command, any invocations of any
environment variables with alphanumeric names, such as $GOFILE or
$HOME, are expanded throughout the command line. The syntax for
variable expansion is $NAME on all operating systems. Due to the
order of evaluation, variables are expanded even inside quoted
strings. If the variable NAME is not set, $NAME expands to the
empty string.

A directive of the form,

	// go:generate-command xxx args…

specifies, for the remainder of this source file only, that the
string xxx represents the command identified by the arguments. This
can be used to create aliases or to handle multiword generators.
For example,

	// go:generate-command foo go tool foo 

specifies that the command "foo" represents the generator
"go tool foo".

Generate processes packages in the order given on the command line,
one at a time. If the command line lists .go files from a single directory,
they are treated as a single package. Within a package, generate processes the
source files in a package in file name order, one at a time. Within
a source file, generate runs generators in the order they appear
in the file, one at a time. The go generate tool also sets the build
tag "generate" so that files may be examined by go generate but ignored
during build.

For packages with invalid code, generate processes only source files with a
valid package clause.

If any generator returns an error exit status, "go generate" skips
all further processing for that package.

The generator is run in the package's source directory.

Go generate accepts one specific flag:

	-run=""
		if non-empty, specifies a regular expression to select
		directives whose full original source text (excluding
		any trailing spaces and final newline) matches the
		expression.

It also accepts the standard build flags including -v, -n, and -x.
The -v flag prints the names of packages and files as they are
processed.
The -n flag prints commands that would be executed.
The -x flag prints commands as they are executed.

For more about build flags, see 'go help build'.

For more about specifying packages, see 'go help packages'.
	`,
}

var (
	generateRunFlag string         // generate-run flag 
	generateRunRE   *regexp.Regexp // run 
)

func init() {
	work.AddBuildFlags(CmdGenerate, work.DefaultBuildFlags)
	CmdGenerate.Flag.StringVar(&generateRunFlag, "run", "", "")
}

func runGenerate(ctx context.Context, cmd *base.Command, args []string) {
	if generateRunFlag != "" {
		var err error
		generateRunRE, err = regexp.Compile(generateRunFlag)
		if err != nil {
			log.Fatalf("generate: %s", err)
		}
	}

	cfg.BuildContext.BuildTags = append(cfg.BuildContext.BuildTags, "generate")

	printed := false
	pkgOpts := load.PackageOpts{IgnoreImports: true}
	for _, pkg := range load.PackagesAndErrors(ctx, pkgOpts, args) {
		if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main {
			if !printed {
				fmt.Fprintf(os.Stderr, "go: not generating in packages in dependency modules\n")
				printed = true
			}
			continue
		}

		for _, file := range pkg.InternalGoFiles() {
			if !generate(file) {
				break
			}
		}

		for _, file := range pkg.InternalXGoFiles() {
			if !generate(file) {
				break
			}
		}
	}
}

// generate运行generate单个文件的ion指令。
func generate(absFile string) bool {
	src, err := os.ReadFile(absFile)
	if err != nil {
		log.Fatalf("generate: %s", err)
	}

	// 解析包子句
	filePkg, err := parser.ParseFile(token.NewFileSet(), "", src, parser.PackageClauseOnly)
	if err != nil {
		// 无效的包子句-忽略文件。
		return true
	}

	g := &Generator{
		r:        bytes.NewReader(src),
		path:     absFile,
		pkg:      filePkg.Name.String(),
		commands: make(map[string][]string),
	}
	return g.run()
}

// 生成器表示单个Go源文件的状态
// 正在扫描生成器命令。
type Generator struct {
	r        io.Reader
	path     string // 完整根路径名。
	dir      string // 文件的完整根目录。
	file     string // 文件的基本名称。
	pkg      string
	commands map[string][]string
	lineNum  int // 当前行号。
	env      []string
}

// run运行当前文件中的生成器。
func (g *Generator) run() (ok bool) {
	// 下面的处理在失败时调用g.errorf，这会导致恐慌（停止）。
	// 如果遇到错误，我们将中止该包。
	defer func() {
		e := recover()
		if e != nil {
			ok = false
			if e != stop {
				panic(e)
			}
			base.SetExitStatus(1)
		}
	}()
	g.dir, g.file = filepath.Split(g.path)
	g.dir = filepath.Clean(g.dir) // 请不要最后分隔符。
	if cfg.BuildV {
		fmt.Fprintf(os.Stderr, "%s\n", base.ShortPath(g.path))
	}

	// 扫描开始的行“
	// 无法使用bufio.Scanner，因为它无法处理使用generate.
	// 时可能出现的长行。
	input := bufio.NewReader(g.r)
	var err error
	// 每循环一行。
	for {
		g.lineNum++ // 1-索引。
		var buf []byte
		buf, err = input.ReadSlice('\n')
		if err == bufio.ErrBufferFull {
			// 行太长-消耗并忽略。
			if isGoGenerate(buf) {
				g.errorf("directive too long")
			}
			for err == bufio.ErrBufferFull {
				_, err = input.ReadSlice('\n')
			}
			if err != nil {
				break
			}
			continue
		}

		if err != nil {
			// 在EOF检查标记，但不使用final\n.
			if err == io.EOF && isGoGenerate(buf) {
				err = io.ErrUnexpectedEOF
			}
			break
		}

		if !isGoGenerate(buf) {
			continue
		}
		if generateRunFlag != "" {
			if !generateRunRE.Match(bytes.TrimSpace(buf)) {
				continue
			}
		}

		g.setEnv()
		words := g.split(string(buf))
		if len(words) == 0 {
			g.errorf("no arguments to directive")
		}
		if words[0] == "-command" {
			g.setShorthand(words)
			continue
		}
		// 运行命令行。
		if cfg.BuildN || cfg.BuildX {
			fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " "))
		}
		if cfg.BuildN {
			continue
		}
		g.exec(words)
	}
	if err != nil && err != io.EOF {
		g.errorf("error reading %s: %s", base.ShortPath(g.path), err)
	}
	return true
}

func isGoGenerate(buf []byte) bool {
	return bytes.HasPrefix(buf, []byte("// go:generate“）||字节。HasPrefix（buf，[]字节（“
}

// setEnv设置执行
// single go:generate命令时使用的额外环境变量。
func (g *Generator) setEnv() {
	g.env = []string{
		"GOARCH=" + cfg.BuildContext.GOARCH,
		"GOOS=" + cfg.BuildContext.GOOS,
		"GOFILE=" + g.file,
		"GOLINE=" + strconv.Itoa(g.lineNum),
		"GOPACKAGE=" + g.pkg,
		"DOLLAR=" + "$",
	}
	g.env = base.AppendPWD(g.env, g.dir)
}

// 拆分将行拆分为单词，计算引用的
// 字符串并计算环境变量。
// 行中存在初始的
func (g *Generator) split(line string) []string {
	// 解析行，遵循带引号的字符串。
	var words []string
	line = line[len("// go:generate“：len（line）-1]
	// 可能仍然有回车符。
	if len(line) > 0 && line[len(line)-1] == '\r' {
		line = line[:len(line)-1]
	}
	// 每次迭代一个（可能引用）字。
Words:
	for {
		line = strings.TrimLeft(line, " \t")
		if len(line) == 0 {
			break
		}
		if line[0] == '"' {
			for i := 1; i < len(line); i++ {
				c := line[i] // 只查找ASCII，所以这是可以的。
				switch c {
				case '\\':
					if i+1 == len(line) {
						g.errorf("bad backslash")
					}
					i++ // 吸收下一个字节（如果是多字节，我们将在Unquote中得到一个错误）.
				case '"':
					word, err := strconv.Unquote(line[0 : i+1])
					if err != nil {
						g.errorf("bad quoted string")
					}
					words = append(words, word)
					line = line[i+1:]
					// 检查下一个字符是空格还是行尾。
					if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
						g.errorf("expect space after quoted argument")
					}
					continue Words
				}
			}
			g.errorf("mismatched quoted string")
		}
		i := strings.IndexAny(line, " \t")
		if i < 0 {
			i = len(line)
		}
		words = append(words, line[0:i])
		line = line[i:]
	}
	// 如果需要，替换命令。
	if len(words) > 0 && g.commands[words[0]] != nil {
		// 用命令替换第0个单词。
		// 
		// 强制将命令定义的副本复制到
		// 确保单词不会作为g.commands内容的引用。
		tmpCmdWords := append([]string(nil), (g.commands[words[0]])...)
		words = append(tmpCmdWords, words[1:]...)
	}
	// 替换环境变量。wen jian defg
	for i, word := range words {
		words[i] = os.Expand(word, g.expandVar)
	}
	return words
}

var stop = fmt.Errorf("error in generation")

// 然后退出程序（退出状态为1），因为生成在第一个错误时停止
// 。
func (g *Generator) errorf(format string, args ...any) {
	fmt.Fprintf(os.Stderr, "%s:%d: %s\n", base.ShortPath(g.path), g.lineNum,
		fmt.Sprintf(format, args...))
	panic(stop)
}

// expandVar扩展word中的$XXX调用。操作系统将其命名为
// 。扩大
func (g *Generator) expandVar(word string) string {
	w := word + "="
	for _, e := range g.env {
		if strings.HasPrefix(e, w) {
			return e[len(w):]
		}
	}
	return os.Getenv(word)
}

// setShorthand安装一个由-command指令定义的新速记。
func (g *Generator) setShorthand(words []string) {
	// 创建命令速记。
	if len(words) == 1 {
		g.errorf("no command specified for -command")
	}
	command := words[1]
	if g.commands[command] != nil {
		g.errorf("command %q multiply defined", command)
	}
	g.commands[command] = words[2:len(words):len(words)] // force later append to make copy 
}

// exec运行参数指定的命令。第一个单词是
// 命令名本身。
func (g *Generator) exec(words []string) {
	cmd := exec.Command(words[0], words[1:]...)
	// 发电机进出标准应为常规。
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	// 在包目录中运行命令。
	cmd.Dir = g.dir
	cmd.Env = str.StringList(cfg.OrigEnv, g.env)
	err := cmd.Run()
	if err != nil {
		g.errorf("running %q: %s", words[0], err)
	}
}
